﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace BitAndMemoryOperations
{
    class Program
    {
        static int[] largeArray;
        static int[] randomIndex;


        [StructLayout(LayoutKind.Explicit)]
        public struct ByteExtractUnion
        {
            [FieldOffset(0)]
            public byte Byte0;
            [FieldOffset(1)]
            public byte Byte1;
            [FieldOffset(2)]
            public byte Byte2;
            [FieldOffset(3)]
            public byte Byte3;
            [FieldOffset(4)]
            public byte Byte4;
            [FieldOffset(5)]
            public byte Byte5;
            [FieldOffset(6)]
            public byte Byte6;
            [FieldOffset(7)]
            public byte Byte7;
  
            [FieldOffset(0)]
            public ulong Value;
        }



        public static void Main(string[] args)
        {
            Console.WriteLine("Performance Tests");
            Console.WriteLine("  Stopwatch Resolution (nS): " + (1000000000.0 / Stopwatch.Frequency).ToString());

            RunTests(1000000);

            Console.WriteLine("Tests Finished, press any key to stop...");
            Console.ReadKey();
        }

        public static void RunTests(int iterations)
        {
            Console.WriteLine("  Iterations: " + iterations.ToString());
            Stopwatch watch = new Stopwatch();
            ShiftAndAddInt(1, 1, 1); ShiftAndAddInt(1, 1, 1); // Warmup
            Console.WriteLine();
            Console.WriteLine("  Shift and Add with int");
            watch.Reset();
            watch.Start();
            for (int i = 0; i < iterations; ++i)
                ShiftAndAddInt(1, 1, 1);

            watch.Stop();
            Console.WriteLine("  Shift and Add with int Elapsed Time (mS): " + ((double)watch.ElapsedTicks / Stopwatch.Frequency * 1000).ToString());

            ShiftAndAddLong(1, 1, 1); ShiftAndAddLong(1, 1, 1); // Warmup

            Console.WriteLine();
            Console.WriteLine("  Shift and Add with long");
            watch.Reset();
            watch.Start();
            for (int i = 0; i < iterations; ++i)
                ShiftAndAddLong(1, 1, 1);

            watch.Stop();
            Console.WriteLine("  Shift and Add with long Elapsed Time (mS): " + ((double)watch.ElapsedTicks / Stopwatch.Frequency * 1000).ToString());




            Console.WriteLine();
            Console.WriteLine("  Shift and Add with uint");
            ShiftAndAddUInt(1, 1, 1); ShiftAndAddUInt(1, 1, 1); // Warmup
            watch.Reset();
            watch.Start();
            for (int i = 0; i < iterations; ++i)
                ShiftAndAddUInt(1, 1, 1);

            watch.Stop();
            Console.WriteLine("  Shift and Add with uint Elapsed Time (mS): " + ((double)watch.ElapsedTicks / Stopwatch.Frequency * 1000).ToString());

            Console.WriteLine();
            Console.WriteLine("  Shift and Add with ulong");
            ShiftAndAddULong(1, 1, 1); ShiftAndAddULong(1, 1, 1); // Warmup 
            watch.Reset();
            watch.Start();
            for (int i = 0; i < iterations; ++i)
                ShiftAndAddULong(1,1,1);

            watch.Stop();
            Console.WriteLine("  Shift and Add with ulong Elapsed Time (mS): " + ((double)watch.ElapsedTicks / Stopwatch.Frequency * 1000).ToString());

            Console.WriteLine();
            Console.WriteLine("  Extract Bytes Par1");
            ExtractBytesPar1(0x0102040810204080); ExtractBytesPar1(0x0102040810204080); // Warmup 
            watch.Reset();
            watch.Start();
            for (int i = 0; i < iterations; ++i)
                ExtractBytesPar1(0x0102040810204080); 

            watch.Stop();
            Console.WriteLine("  Extract Bytes Par1 Elapsed Time (mS): " + ((double)watch.ElapsedTicks / Stopwatch.Frequency * 1000).ToString());

            Console.WriteLine();
            Console.WriteLine("  Extract Bytes Par2");
            ExtractBytesPar2(0x0102040810204080); ExtractBytesPar2(0x0102040810204080); // Warmup 
            watch.Reset();
            watch.Start();
            for (int i = 0; i < iterations; ++i)
                ExtractBytesPar2(0x0102040810204080);

            watch.Stop();
            Console.WriteLine("  Extract Bytes Par2 Elapsed Time (mS): " + ((double)watch.ElapsedTicks / Stopwatch.Frequency * 1000).ToString());


            Console.WriteLine();
            Console.WriteLine("  Extract Bytes Seq");
            ExtractBytesSeq(0x0102040810204080); ExtractBytesSeq(0x0102040810204080); // Warmup 
            watch.Reset();
            watch.Start();
            for (int i = 0; i < iterations; ++i)
                ExtractBytesSeq(0x0102040810204080);

            watch.Stop();
            Console.WriteLine("  Extract Bytes Seq Elapsed Time (mS): " + ((double)watch.ElapsedTicks / Stopwatch.Frequency * 1000).ToString());


       
         

            largeArray = new int[100000000];
            randomIndex = new int[100000];

            Console.WriteLine();
            Console.WriteLine("  Init Large Data");
            Random rng = new Random();
            int initValue = rng.Next();
            for (int i = 0; i < largeArray.Length; ++i)
            {
                largeArray[i] = initValue;
            }

            Console.WriteLine();
            Console.WriteLine("  Init Random Index Data");
             for (int i = 0; i < randomIndex.Length; ++i)
            {

                randomIndex[i] = rng.Next(0, largeArray.Length-1);
            }

            Console.WriteLine();
            Console.WriteLine("  Array Access");
            int value = rng.Next();
            // Warmup
            for (int i = 0; i < randomIndex.Length; ++i)
                value ^= largeArray[randomIndex[i]];
            
            watch.Reset();
            watch.Start();
            for (int i = 0; i < randomIndex.Length; ++i)
                value ^= largeArray[randomIndex[i]];
            watch.Stop();
            Console.WriteLine("  Array Access Elapsed Time (mS): " + ((double)watch.ElapsedTicks / Stopwatch.Frequency * 1000).ToString() + " (" + value.ToString() + ")");


            Console.WriteLine();
            Console.WriteLine("  Init Random Index Data");
            for (int i = 0; i < randomIndex.Length; ++i)
            {

                randomIndex[i] = rng.Next(0, 1000000);
            }

            Console.WriteLine();
            Console.WriteLine("  Array Access");
            value = rng.Next();
            // Warmup
            for (int i = 0; i < randomIndex.Length; ++i) 
                value ^= largeArray[randomIndex[i]];
            watch.Reset();
            watch.Start();
            for (int i = 0; i < randomIndex.Length; ++i)
                value ^= largeArray[randomIndex[i]];
            watch.Stop();
            Console.WriteLine("  Array Access Elapsed Time (mS): " + ((double)watch.ElapsedTicks / Stopwatch.Frequency * 1000).ToString() + " (" + value.ToString() + ")");

            Console.WriteLine();
            Console.WriteLine("  Init Random Index Data");
            for (int i = 0; i < randomIndex.Length; ++i)
            {

                randomIndex[i] = rng.Next(0, 100000);
            }

            Console.WriteLine();
            Console.WriteLine("  Array Access");
            value = rng.Next();
            // Warmup
            for (int i = 0; i < randomIndex.Length; ++i)
                value ^= largeArray[randomIndex[i]];
            watch.Reset();
            watch.Start();
            for (int i = 0; i < randomIndex.Length; ++i)
                value ^= largeArray[randomIndex[i]];
            watch.Stop();
            Console.WriteLine("  Array Access Elapsed Time (mS): " + ((double)watch.ElapsedTicks / Stopwatch.Frequency * 1000).ToString() + " (" + value.ToString() + ")");


            byte[] sourceArray = new byte[1000000];
            byte[] destArray   = new byte[1000000];
            Console.WriteLine();
            Console.WriteLine("  Array Copy");
            // Warmup
            ArrayCopy(sourceArray, destArray); ArrayCopy(sourceArray, destArray);
            watch.Reset();
            watch.Start();
            for (int i = 0; i < 10000; ++i)
                ArrayCopy(sourceArray, destArray);
                
            watch.Stop();
            Console.WriteLine("  Array Copy Elapsed Time (mS): " + ((double)watch.ElapsedTicks / Stopwatch.Frequency * 1000).ToString() + " (" + value.ToString() + ")");

            Console.WriteLine();
            Console.WriteLine("  Block Copy");
            // Warmup
            BlockCopy(sourceArray, destArray); BlockCopy(sourceArray, destArray);
            watch.Reset();
            watch.Start();
            for (int i = 0; i < 10000; ++i)
                BlockCopy(sourceArray, destArray);

            watch.Stop();
            Console.WriteLine("  Block Copy Elapsed Time (mS): " + ((double)watch.ElapsedTicks / Stopwatch.Frequency * 1000).ToString() + " (" + value.ToString() + ")");

        }

        public static void BlockCopy(byte[] source, byte[] dest)
        {
            System.Buffer.BlockCopy(source, 0, dest, 0, source.Length);
        }


        public static void ArrayCopy(byte[] source, byte[] dest)
        {
            System.Array.Copy(source, dest, source.Length);
        }

        public static byte ExtractBytesPar1(ulong value)
        {
            byte result;
            result = unchecked( (byte) value);
            result |= unchecked((byte) (value >> 8));
            result |= unchecked((byte)(value >> 16));
            result |= unchecked((byte)(value >> 24));
            result |= unchecked((byte)(value >> 32));
            result |= unchecked((byte)(value >> 40));
            result |= unchecked((byte)(value >> 48));
            result |= unchecked((byte)(value >> 56));
            return result;
        }

        public static byte ExtractBytesPar2(ulong value)
        {
            byte result;
            byte result1;
            result = unchecked((byte)value);
            result ^= unchecked((byte)(value >> 8));
            result ^= unchecked((byte)(value >> 16));
            result ^= unchecked((byte)(value >> 24));
            result1 = unchecked((byte)(value >> 32));
            result1 ^= unchecked((byte)(value >> 40));
            result1 ^= unchecked((byte)(value >> 48));
            result1 ^= unchecked((byte)(value >> 56));
            result ^= result1;
            return result;
        }

        public static byte ExtractBytesSeq(ulong value)
        {
            byte result;
            result = unchecked((byte)value); value >>= 8;
            result |= unchecked((byte)value); value >>= 8;
            result |= unchecked((byte)value); value >>= 8;
            result |= unchecked((byte)value); value >>= 8;
            result |= unchecked((byte)value); value >>= 8;
            result |= unchecked((byte)value); value >>= 8;
            result |= unchecked((byte)value); value >>= 8;
            result |= unchecked((byte)value); 
            return result;
        }


        public static ulong ShiftAndAddInt(uint seed, uint add, int shift)
        {
            seed <<= shift; seed += add; seed <<= shift; seed += add;
            seed <<= shift; seed += add; seed <<= shift; seed += add;
            seed <<= shift; seed += add; seed <<= shift; seed += add;
            seed <<= shift; seed += add; seed <<= shift; seed += add;
            seed <<= shift; seed += add; seed <<= shift; seed += add;
            seed <<= shift; seed += add; seed <<= shift; seed += add;
            return seed;
        }


        public static ulong ShiftAndAddLong(uint seed, uint add, int shift)
        {
            seed <<= shift; seed += add; seed <<= shift; seed += add;
            seed <<= shift; seed += add; seed <<= shift; seed += add;
            seed <<= shift; seed += add; seed <<= shift; seed += add;
            seed <<= shift; seed += add; seed <<= shift; seed += add;
            seed <<= shift; seed += add; seed <<= shift; seed += add;
            seed <<= shift; seed += add; seed <<= shift; seed += add;
            return seed;
        }


        public static ulong ShiftAndAddUInt(uint seed, uint add, int shift)
        {
            seed <<= shift; seed += add; seed <<= shift; seed += add;
            seed <<= shift; seed += add; seed <<= shift; seed += add;
            seed <<= shift; seed += add; seed <<= shift; seed += add;
            seed <<= shift; seed += add; seed <<= shift; seed += add;
            seed <<= shift; seed += add; seed <<= shift; seed += add;
            seed <<= shift; seed += add; seed <<= shift; seed += add;
            return seed;
        }


        public static ulong ShiftAndAddULong(ulong seed, ulong add, int shift )
        {
            seed <<= shift; seed += add; seed <<= shift; seed += add;
            seed <<= shift; seed += add; seed <<= shift; seed += add;
            seed <<= shift; seed += add; seed <<= shift; seed += add;
            seed <<= shift; seed += add; seed <<= shift; seed += add;
            seed <<= shift; seed += add; seed <<= shift; seed += add;
            seed <<= shift; seed += add; seed <<= shift; seed += add; 
            return seed;
        }

    }
}
